package com.bdhub.handld;

import com.alibaba.fastjson.JSON;
import com.bdhub.common.security.MD5;
import com.bdhub.im.server.protobuf.MessageProtobuf.CMsg;
import com.bdhub.im.server.protobuf.MessageProtobuf.CMsgHead;
import com.bdhub.interfaces.EventListener;
import com.bdhub.interfaces.StatusEventHandle;
import com.bdhub.tools.JsonUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;

import bdhub.littletail.lib.utils.StringUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.timeout.ReadTimeoutHandler;

public class ClientBootstrap {
	private static Log logger = LogFactory.getLog(ClientBootstrap.class);
	private Bootstrap bootstrap = null;
	private Channel channel = null;
	private String tokenstr = null;
	// 服务器列表
	private Vector<String> addrs = null;
	private ExecutorServiceFactory poolFactory = new ExecutorServiceFactory();
	private MessageTimeOutManager timeOutManager = new MessageTimeOutManager(
			this);
	private MsgDispatcher dispatcher = new MsgDispatcher(this);
	private MsgProcesser processer = new MsgProcesser(this);
	// 网络连接状态回调
	private NetStatusCallback netStatusCallback;
	private EventListener listener;

	private static final int CONNECT_OK = 0;
	private static final int CONNECT_FAIL = -1;
	private int CONNECT_STATUS = CONNECT_FAIL;

	// 启动重连标识
	private boolean isReconRunning = false;

	// 延时时长
	private long delayTime = 1000;

	// 连接超时
	private int connTimeout = 10 * 1000;

	// 重发次数
	private long sendTime = 3;

	// APP状态：0,前台。-1,后台
	private int appStatus = 0;

	public int getAppStatus() {
		return this.appStatus;
	}

	/*
	 * 前后台切换 status:0,切换到前台。-1,切换到后台
	 */
	public void setAppStatus(int status) {
		this.appStatus = status;
	}

	public long getSendTime() {
		return sendTime;
	}

	public void setSendTime(long sendTime) {
		this.sendTime = sendTime;
	}

	public long getSendFreq() {
		return sendFreq;
	}

	public void setSendFreq(long sendFreq) {
		this.sendFreq = sendFreq;
	}

	// 重发间隔
	private long sendFreq = 5 * 1000;

	public int getConnTimeout() {
		return connTimeout;
	}

	public void setConnTimeout(int connTimeout) {
		this.connTimeout = connTimeout;
	}

	// 延时时长为毫秒
	public void setDelayTime(long delayTime) {
		this.delayTime = delayTime;
	}

	private void closeChannel() {
		try {
			if (channel != null) {
				try {
					channel.pipeline().remove("read");
					channel.pipeline().remove("write");
				} catch (Exception e) {
				} finally {
					try {
						channel.close();
					} catch (Exception ex) {
					}
					try {
						channel.eventLoop().shutdownGracefully();
					} catch (Exception ex) {
					}
					channel = null;
				}
			}
		} catch (Exception ex) {
			logger.error("关闭连接信息出错", ex);
		}
	}

	// 重连
	public void resetConnect() {
		resetConnect(false);
	}

	/**
	 * 
	 * @param isFirst
	 *            是否首次重连
	 */
	public void resetConnect(boolean isFirst) {
		if (!isFirst) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		/*
		 * 只有第一个调用者才能赋值并调用重新连接
		 */
		if (!isClose && !isReconRunning) {
			synchronized (this) {
				if (!isClose && !isReconRunning) {
					isReconRunning = true;
					CONNECT_STATUS = CONNECT_FAIL;
					// 先关闭channel
					closeChannel();
					// 启动重连线程
					execute(new ReConnThread());
				}
			}

		}
	}

	// 设置网络状态
	public void setNetStatus(boolean netStatus) {
		// this.netStatus = netStatus;
	}

	public boolean isNetworkConnected() {
		return listener.isNetworkConnected();
	}

	public synchronized void addTimeOutHandler(int outTime) {
		try {
			if (channel != null) {
				String handerName = "idleStateHandler";
				if (channel.pipeline().get(handerName) != null) {
					channel.pipeline().remove(handerName);
				}
				channel.pipeline().addFirst(handerName,
						new ReadTimeoutHandler(outTime));
			}
		} catch (Exception ex) {
			logger.error("添加心跳超时时间监听异常", ex);
		}
	}

	public ClientBootstrap() {
	}

	// public static ClientBootstrap getInstance() {
	// return clientBootstrap;
	// }

	/*
	 * param addr 服务器列表 param list 监听器 param uid param token
	 */
	public synchronized void init(Vector<String> addr, EventListener listener,
								  String uid, String token, StatusEventHandle statusEventHandle) {
		close();
		isClose = false;
		addrs = addr;
		fmid = uid;
		tokenstr = token;
		this.listener = listener;
		// 线程池
		poolFactory.initBossPool(1);
		netStatusCallback = new NetStatusCallback(statusEventHandle);
		// 初始化netty
		// initBootstrap();
		resetConnect(true);
		// 启动监听器，回调消息给应用层
		dispatcher.setListener(listener);
	}

	private void toServer(String host, int port) {
		try {
			channel = bootstrap.connect(host, port).sync().channel();
			// poolFactory.initWorkPool(2);
		} catch (Exception e) {
			logger.error(String.format("连接Server(IP[%s],PORT[%s])失败", host,
					port));
			channel = null;
		}
	}

	/**
	 * 初始化Bootstrap
	 * 
	 * @return
	 */
	private void initBootstrap() {
		final ClientBootstrap clientBootstrap = this;
		EventLoopGroup group = new NioEventLoopGroup(2);
		bootstrap = new Bootstrap();
		bootstrap.group(group).channel(NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.option(ChannelOption.TCP_NODELAY, true);
		bootstrap
				.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnTimeout());
		bootstrap.handler(new ChannelInitializer<Channel>() {
			@Override
			protected void initChannel(Channel ch) throws Exception {
				ChannelPipeline pipeline = ch.pipeline();
				pipeline.addLast("frameDecoder",
						new LengthFieldBasedFrameDecoder(10000000, 0, 4, 0, 4));
				pipeline.addLast(new ProtobufDecoder(CMsg
						.getDefaultInstance()));
				pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
				pipeline.addLast(new ProtobufEncoder());
				pipeline.addLast("read", new ReadHandler(clientBootstrap));
				pipeline.addLast("write", new WritetHandler());
			}
		});
	}

	// 发送消息
	public void addMsg(CMsg msg) {
		if (!isClose) {
			// 建立定时器，超过时间则执行--重新连接,3002除外
			try {
				// 拉取离线与拉取历史消息采用gzip压缩
				if (msg.getHead().getType() == 1010
						|| msg.getHead().getType() == 1012) {
					Map<String, Object> params = null;
					if (StringUtil.isBlank(msg.getHead().getComments())) {
						params = new HashMap<String, Object>();
					} else {
						params = JsonUtils.getMapByJsonStr(msg.getHead()
								.getComments());
					}
					params.put("encoder", "gzip");
					msg = msg
							.toBuilder()
							.setHead(
									msg.getHead()
											.toBuilder()
											.setComments(
													JsonUtils
															.getJsonStrByMap(params))
											.build()).build();
				}
				if (3002 != msg.getHead().getType()
						&& 3003 != msg.getHead().getType()
						&& 1000 != msg.getHead().getType()) {
					if (!StringUtil.isBlank(msg.getHead().getMid())) {
						timeOutManager.add(msg);
					}
				}
				sendMsg(msg);
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		} else {
			System.err.println("imlib is close");
		}
	}

	public void execute(Runnable thread) {
		poolFactory.executeBoss(thread);
	}

	// 加入接收队列
	// public void addRecMsg(Message.Builder msg)
	// {
	// recvQ.offer(msg.build());
	// }

	public void sendMsg(CMsg msg) {
		try {
			if (null == channel) {
				return;
			}
			channel.writeAndFlush(msg);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public CMsg buildLoginMsg() {
		long t = System.nanoTime();
		String etoken = StringUtil.bytesToHexString(tokenstr.getBytes());
		UUID uuid = UUID.randomUUID();
		// build message
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("timestamp", t);
		map.put("authCode", MD5.convert32(fmid + etoken + t));

		String strjson = JSON.toJSONString(map);
		CMsgHead.Builder head = CMsgHead.newBuilder();
		head.setType(1001);
		head.setMid(uuid.toString());
		head.setFromId(fmid);
		head.setComments(strjson);

		CMsg.Builder msg = CMsg.newBuilder();
		msg.setHead(head);
		//

		return msg.build();
	}

	/**
	 * 连接后回调
	 */
	private int connectCallback(int status) {
		CONNECT_STATUS = status;
		// 连接成功
		if (CONNECT_STATUS == CONNECT_OK) {
			// 发送握手消息
			addMsg(this.buildLoginMsg());
		} else {
			/**
			 * 连接失败同步阻塞段时间再发1004消息通知应用层
			 */
			try {
				long sleepTime = 0;
				while (sleepTime <= this.delayTime) {
					if (isClose) {
						return CONNECT_STATUS;
					}
					sleepTime += 1000;
					Thread.sleep(1000);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			UUID uuid = UUID.randomUUID();
			CMsgHead.Builder head = CMsgHead.newBuilder();
			head.setType(1004);
			head.setMid(uuid.toString());
			CMsg.Builder msg = CMsg.newBuilder();
			msg.setHead(head);
			dispatcher.receiveMsg(msg.build());
		}
		return CONNECT_STATUS;
	}

	private static String fmid = null;
	// 关闭标识
	public static boolean isClose = false;

	public String getFmid() {
		return fmid;
	}

	public boolean isClose() {
		return isClose;
	}

	/*
	 * 关闭连接 释放资源
	 */
	public synchronized void close() {
		if (isClose) {
			return;
		}
		isClose = true;
		// 确保接收数据都处理完成
		try {
			closeChannel();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		try {
			if (bootstrap != null) {
				bootstrap.group().shutdownGracefully();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		try {
			// 释放线程 池
			poolFactory.destory();
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			try {
				if (timeOutManager != null) {
					timeOutManager.clear();
				}

				if (addrs != null) {
					addrs.clear();
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			channel = null;
			isReconRunning = false;
			appStatus = 0;
			bootstrap = null;
		}
	}

	// 设置地址
	public void setAddrs(Vector<String> addr) {
		addrs = addr;
	}

	/*
	 * 重新连接 根据应用层给的网络状态及底层自己的网络状态。 建立循环 重连成功或关闭连接时将退出循环并退出线程
	 */
	private class ReConnThread implements Runnable {

		private ReConnThread() {
		}

		public void run() {
			try {
				netStatusCallback.setStatus(CONNECT_FAIL);
				// 重连时，释放工作线程
				poolFactory.destoryWork();
				// 当关闭连接时，退出循环
				while (!isClose) {
					// 异常捕捉，有任何异常，只要没有连接成功，重连线程都不能结束
					try {
						// 有网时
						if (isNetworkConnected()) {
							// 重连成功，退出线程
							if (connectCallback(reConnect()) == CONNECT_OK) {
								break;
							} else {
								netStatusCallback.setStatus(CONNECT_FAIL);
							}
						}
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();// 线程被中断，则制退出
						break;
					}
				}
				netStatusCallback.setStatus(CONNECT_STATUS);
			} finally {
				isReconRunning = false;
			}
		}

		// 重连只涉及网络，与其他无关。
		private int reConnect() {
			// 需要加入网络状态，根据网络状态进行重连
			if (!isClose) {
				try {
					if (bootstrap != null) {
						bootstrap.group().shutdownGracefully();
					}
				} catch (Exception ex) {
					ex.printStackTrace();
				} finally {
					bootstrap = null;
				}
				initBootstrap();
				// 连接服务器
				return toConnect();
			}
			return CONNECT_FAIL;
		}

		/**
		 * 连接服务器
		 * 
		 * @return 连接状态
		 */
		private int toConnect() {
			// 连接成功标识
			for (int i = 0; (!isClose && i < addrs.size()); i++) {
				String s = addrs.get(i);
				String a[] = s.split(" ");
				for (int j = 0; j < 3; j++) {
					if (isClose || !isNetworkConnected()) {
						return CONNECT_FAIL;
					}
					toServer(a[0], Integer.parseInt(a[1]));
					// 连接上服务器，先发握手消息
					if (null != channel) {
						return CONNECT_OK;
					}
				}
			}
			return CONNECT_FAIL;

		}

	}

	public void processMsg(CMsg message) {
		this.processer.processMsg(message);
	}

	public MsgDispatcher getDispatcher() {
		return dispatcher;
	}

	public MsgProcesser getProcesser() {
		return processer;
	}

	public ExecutorServiceFactory getPoolFactory() {
		return poolFactory;
	}

	public MessageTimeOutManager getMessageTimeOutManager() {
		return this.timeOutManager;
	}

}
